use std::{
    cmp::Ordering,
    collections::HashMap,
    error::Error,
    fmt::{self, Display, Formatter},
    path::{Path, PathBuf},
};

use inkwell::{
    basic_block::BasicBlock,
    builder::{Builder, BuilderError},
    context::Context,
    module::{Linkage, Module},
    targets::{FileType, TargetMachine},
    types::{BasicMetadataTypeEnum, FunctionType, IntType, PointerType},
    values::{BasicMetadataValueEnum, FunctionValue, IntValue},
    AddressSpace, IntPredicate,
};

use crate::{
    ast::{
        BinaryOperationNode, BitOperationBinaryOperationNode, BlockNode,
        CompareBinaryOperationNode, EqualityBinaryOperationNode, ExpressionNode, JumpTarget,
        LiteralNode, LogicalBinaryOperationNode, MatchNode, MathematicsBinaryOperationNode,
        ProtoFile, ProtoNode, RuleNode, UnaryOperationNode,
    },
    error::ErrorLocation,
    record::{RecordHandler, RecordItem},
    scope::GrammarScope,
    CompileError, CompileTarget, CompileTargetError,
};

#[derive(Debug)]
pub enum SymbolType {
    Int,
    Bool,
    SizedInt(u8),
}

#[derive(Debug)]
pub enum CompileModuleError {
    TooLargeNumber(u64),
    InvalidIntSize(u32),
    BadUpcast {
        size: u8,
        target: u8,
    },
    UnknownSymbol(String),
    BuildFailed(BuilderError),
    InvalidType {
        expected: SymbolType,
        actual: SymbolType,
    },
    CompareDifferentType {
        left: SymbolType,
        right: SymbolType,
    },
    UnknownFunction(String),
    UnknownMatch(String),
    TrailingOperation,
    MissingStop,
    TooManyRecordItem(usize),
}

impl Display for CompileModuleError {
    fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result {
        // Improve error message printing
        todo!()
    }
}

impl Error for CompileModuleError {}

impl<'input, Target: CompileTarget> From<CompileModuleError> for CompileError<'input, Target>
where
    Target: CompileTarget<Error = CompileModuleError>,
{
    fn from(value: CompileModuleError) -> Self {
        CompileError::Compile(value)
    }
}

impl From<BuilderError> for CompileModuleError {
    fn from(value: BuilderError) -> Self {
        CompileModuleError::BuildFailed(value)
    }
}

impl CompileModuleError {
    fn error_location(&self) -> Option<ErrorLocation> {
        todo!()
    }
}

impl<Target> CompileTargetError<Target> for CompileModuleError
where
    Target: CompileTarget<Error = CompileModuleError>,
{
    fn location(&self) -> Option<ErrorLocation> {
        self.error_location()
    }
}

fn get_int_type(context: &Context, length: u8) -> Result<IntType, CompileModuleError> {
    match length {
        0 => Err(CompileModuleError::InvalidIntSize(0)),
        1 => Ok(context.i8_type()),
        2 => Ok(context.i16_type()),
        4 => Ok(context.i32_type()),
        8 => Ok(context.i64_type()),
        16 => Ok(context.i128_type()),
        other => Ok(context.custom_width_int_type(other as u32 * 8)),
    }
}

#[derive(Debug, Clone)]
struct IntSymbolItem<'ctx> {
    size: u8,
    value: IntValue<'ctx>,
}

impl<'ctx> IntSymbolItem<'ctx> {
    fn upcast(
        self,
        context: &'ctx Context,
        builder: &Builder<'ctx>,
        size: u8,
    ) -> Result<IntSymbolItem<'ctx>, CompileModuleError> {
        match self.size.cmp(&size) {
            Ordering::Less => {
                let target_type = get_int_type(context, size)?;
                let casted = builder.build_int_cast(self.value, target_type, "")?;
                Ok(IntSymbolItem {
                    size,
                    value: casted,
                })
            }
            Ordering::Equal => Ok(self),
            Ordering::Greater => Err(CompileModuleError::BadUpcast {
                size: self.size,
                target: size,
            }),
        }
    }

    fn upcast_item(
        self,
        other: IntSymbolItem<'ctx>,
        context: &'ctx Context,
        builder: &Builder<'ctx>,
    ) -> Result<(IntSymbolItem<'ctx>, IntSymbolItem<'ctx>, u8), CompileModuleError> {
        let target_size = self.size.max(other.size);
        let new_self = self.upcast(context, builder, target_size)?;
        let new_other = other.upcast(context, builder, target_size)?;
        Ok((new_self, new_other, target_size))
    }
}

#[derive(Debug, Clone)]
enum SymbolItem<'ctx> {
    Int(IntSymbolItem<'ctx>),
    Bool { value: IntValue<'ctx> },
}

impl<'ctx> SymbolItem<'ctx> {
    fn from_literal<'input>(
        context: &'ctx Context,
        literal: &'input LiteralNode,
    ) -> Result<Self, CompileModuleError> {
        match literal {
            LiteralNode::Int(value) => {
                let bytes = value.bits().div_ceil(8);
                let bytes: u8 = bytes
                    .try_into()
                    .map_err(|_| CompileModuleError::TooLargeNumber(bytes))?;
                let int_type = get_int_type(context, bytes)?;
                let (_, words) = value.to_bytes_be();
                let words: Vec<u64> = words
                    .rchunks(8)
                    .map(|chunk| {
                        if chunk.len() < 8 {
                            let padding = 8 - chunk.len();
                            let padded: Vec<u8> = (0..padding)
                                .map(|_| 0)
                                .chain(chunk.iter().copied())
                                .collect();
                            padded.try_into().unwrap()
                        } else {
                            chunk.try_into().unwrap()
                        }
                    })
                    .map(u64::from_be_bytes)
                    .collect();
                let value = int_type.const_int_arbitrary_precision(&words);
                Ok(SymbolItem::Int(IntSymbolItem { size: bytes, value }))
            }
            LiteralNode::Bool(value) => {
                let bool_type = context.bool_type();
                let value = if *value {
                    bool_type.const_all_ones()
                } else {
                    bool_type.const_zero()
                };
                Ok(SymbolItem::Bool { value })
            }
        }
    }

    fn get_type(&self) -> SymbolType {
        match self {
            SymbolItem::Int(IntSymbolItem { size, value: _ }) => SymbolType::SizedInt(*size),
            SymbolItem::Bool { value: _ } => SymbolType::Bool,
        }
    }

    fn try_into_int(self) -> Result<IntSymbolItem<'ctx>, CompileModuleError> {
        if let SymbolItem::Int(item) = self {
            Ok(item)
        } else {
            Err(CompileModuleError::InvalidType {
                expected: SymbolType::Int,
                actual: self.get_type(),
            })
        }
    }

    fn try_into_bool(self) -> Result<IntValue<'ctx>, CompileModuleError> {
        if let SymbolItem::Bool { value } = self {
            Ok(value)
        } else {
            Err(CompileModuleError::InvalidType {
                expected: SymbolType::Bool,
                actual: self.get_type(),
            })
        }
    }
}

#[derive(Debug)]
pub struct LlvmModuleTarget<'ctx> {
    context: &'ctx Context,
    machine: &'ctx TargetMachine,
}

impl<'ctx> LlvmModuleTarget<'ctx> {
    pub fn new(context: &'ctx Context, machine: &'ctx TargetMachine) -> Self {
        Self { context, machine }
    }
}

struct CompileContext<'a, 'ctx, Handler: RecordHandler> {
    system_functions: &'a SystemFunctions<'ctx>,
    proto_name: &'a str,
    record_handler: &'a mut Handler,
    ptr_type: PointerType<'ctx>,
    ptr_sized_int_type: IntType<'ctx>,
    fn_type: FunctionType<'ctx>,
    fn_value: FunctionValue<'ctx>,
}

struct SystemFunctions<'ctx> {
    emit_record: FunctionValue<'ctx>,
}

impl<'ctx> CompileTarget for LlvmModuleTarget<'ctx> {
    type Result = Module<'ctx>;

    type Error = CompileModuleError;

    fn compile_ast<'input>(
        &self,
        ast: &'input ProtoFile,
        proto_name: &'input str,
        record_handler: &mut impl RecordHandler,
    ) -> Result<Self::Result, Self::Error> {
        let target_data = self.machine.get_target_data();

        let void_type = self.context.void_type();
        let ptr_type = self.context.ptr_type(AddressSpace::default());
        let ptr_sized_int_type = self.context.ptr_sized_int_type(&target_data, None);
        let fn_type = void_type.fn_type(
            &[
                BasicMetadataTypeEnum::PointerType(ptr_type),
                BasicMetadataTypeEnum::IntType(ptr_sized_int_type),
            ],
            false,
        );

        let module: Module<'ctx> = self.context.create_module(proto_name);

        let emit_record_type = void_type.fn_type(
            &[
                BasicMetadataTypeEnum::IntType(self.context.i16_type()),
                BasicMetadataTypeEnum::PointerType(ptr_type),
                BasicMetadataTypeEnum::IntType(self.context.i16_type()),
            ],
            false,
        );
        let system_functions = SystemFunctions {
            emit_record: module.add_function(
                "emit_record",
                emit_record_type,
                Some(Linkage::External),
            ),
        };

        let mut matches = HashMap::new();
        let mut functions = HashMap::new();
        for node in &ast.nodes {
            match node {
                ProtoNode::Match(node) => {
                    matches.insert(node.name, node);
                }
                ProtoNode::Chain(node) => {
                    let fn_value = module.add_function(
                        &format!("{}_{}", proto_name, node.name),
                        fn_type,
                        Some(match node.name {
                            "main" => Linkage::External,
                            _ => Linkage::Private,
                        }),
                    );
                    functions.insert(node.name, fn_value);
                }
            };
        }

        fn build_expression<'ctx, 'input>(
            context: &'ctx Context,
            scope: &mut GrammarScope<'_, 'input, SymbolItem<'ctx>>,
            builder: &Builder<'ctx>,
            expression: &ExpressionNode<'input>,
        ) -> Result<SymbolItem<'ctx>, CompileModuleError> {
            match expression {
                ExpressionNode::Unary { op, expr } => {
                    let expr_value = build_expression(context, scope, builder, expr)?;
                    match op {
                        UnaryOperationNode::LogicNot => {
                            let expr_value = expr_value.try_into_bool()?;
                            let result = builder.build_not(expr_value, "")?;
                            Ok(SymbolItem::Bool { value: result })
                        }
                        UnaryOperationNode::Not => {
                            let expr_value = expr_value.try_into_int()?;
                            let result = builder.build_not(expr_value.value, "")?;
                            Ok(SymbolItem::Int(IntSymbolItem {
                                size: expr_value.size,
                                value: result,
                            }))
                        }
                    }
                }
                ExpressionNode::Binary { op, left, right } => {
                    let left_value = build_expression(context, scope, builder, left)?;
                    let right_value = build_expression(context, scope, builder, right)?;
                    match op {
                        BinaryOperationNode::Logical(logical) => {
                            let left_value = left_value.try_into_bool()?;
                            let right_value = right_value.try_into_bool()?;
                            let result = match logical {
                                LogicalBinaryOperationNode::LogicAnd => {
                                    builder.build_and(left_value, right_value, "")
                                }
                                LogicalBinaryOperationNode::LogicOr => {
                                    builder.build_or(left_value, right_value, "")
                                }
                            }?;
                            Ok(SymbolItem::Bool { value: result })
                        }
                        BinaryOperationNode::Equality(equality) => {
                            let (left_value, right_value) = match (left_value, right_value) {
                                (SymbolItem::Int(left_value), SymbolItem::Int(right_value)) => {
                                    let (left_value, right_value, _) =
                                        left_value.upcast_item(right_value, context, builder)?;
                                    (left_value.value, right_value.value)
                                }
                                (
                                    SymbolItem::Bool { value: left_value },
                                    SymbolItem::Bool { value: right_value },
                                ) => (left_value, right_value),
                                (left_value, right_value) => {
                                    return Err(CompileModuleError::CompareDifferentType {
                                        left: left_value.get_type(),
                                        right: right_value.get_type(),
                                    })
                                }
                            };
                            let result = builder.build_int_compare(
                                match equality {
                                    EqualityBinaryOperationNode::Equals => IntPredicate::EQ,
                                    EqualityBinaryOperationNode::NotEquals => IntPredicate::NE,
                                },
                                left_value,
                                right_value,
                                "",
                            )?;
                            Ok(SymbolItem::Bool { value: result })
                        }
                        BinaryOperationNode::Mathematics(mathematics) => {
                            let left_value = left_value.try_into_int()?;
                            let right_value = right_value.try_into_int()?;
                            let (left_value, right_value, target_size) =
                                left_value.upcast_item(right_value, context, builder)?;
                            let result = match mathematics {
                                MathematicsBinaryOperationNode::Add => {
                                    builder.build_int_add(left_value.value, right_value.value, "")
                                }
                                MathematicsBinaryOperationNode::Minus => {
                                    builder.build_int_sub(left_value.value, right_value.value, "")
                                }
                                MathematicsBinaryOperationNode::Multiply => {
                                    builder.build_int_mul(left_value.value, right_value.value, "")
                                }
                                MathematicsBinaryOperationNode::Divide => builder
                                    .build_int_unsigned_div(
                                        left_value.value,
                                        right_value.value,
                                        "",
                                    ),
                                MathematicsBinaryOperationNode::Mod => builder
                                    .build_int_unsigned_rem(
                                        left_value.value,
                                        right_value.value,
                                        "",
                                    ),
                            }?;
                            Ok(SymbolItem::Int(IntSymbolItem {
                                size: target_size,
                                value: result,
                            }))
                        }
                        BinaryOperationNode::BitOperation(bit_operation) => {
                            let left_value = left_value.try_into_int()?;
                            let right_value = right_value.try_into_int()?;
                            match bit_operation {
                                BitOperationBinaryOperationNode::And => {
                                    let (left_value, right_value, target_size) =
                                        left_value.upcast_item(right_value, context, builder)?;
                                    let result = builder.build_and(
                                        left_value.value,
                                        right_value.value,
                                        "",
                                    )?;
                                    Ok(SymbolItem::Int(IntSymbolItem {
                                        size: target_size,
                                        value: result,
                                    }))
                                }
                                BitOperationBinaryOperationNode::Or => {
                                    let (left_value, right_value, target_size) =
                                        left_value.upcast_item(right_value, context, builder)?;
                                    let result = builder.build_or(
                                        left_value.value,
                                        right_value.value,
                                        "",
                                    )?;
                                    Ok(SymbolItem::Int(IntSymbolItem {
                                        size: target_size,
                                        value: result,
                                    }))
                                }
                                BitOperationBinaryOperationNode::Xor => {
                                    let (left_value, right_value, target_size) =
                                        left_value.upcast_item(right_value, context, builder)?;
                                    let result = builder.build_xor(
                                        left_value.value,
                                        right_value.value,
                                        "",
                                    )?;
                                    Ok(SymbolItem::Int(IntSymbolItem {
                                        size: target_size,
                                        value: result,
                                    }))
                                }
                                BitOperationBinaryOperationNode::LeftShift => {
                                    let result = builder.build_left_shift(
                                        left_value.value,
                                        right_value.value,
                                        "",
                                    )?;
                                    Ok(SymbolItem::Int(IntSymbolItem {
                                        size: left_value.size,
                                        value: result,
                                    }))
                                }
                                BitOperationBinaryOperationNode::RightShift => {
                                    let result = builder.build_right_shift(
                                        left_value.value,
                                        right_value.value,
                                        false,
                                        "",
                                    )?;
                                    Ok(SymbolItem::Int(IntSymbolItem {
                                        size: right_value.size,
                                        value: result,
                                    }))
                                }
                            }
                        }
                        BinaryOperationNode::Compare(compare) => {
                            let left_value = left_value.try_into_int()?;
                            let right_value = right_value.try_into_int()?;
                            let (left_value, right_value, _) =
                                left_value.upcast_item(right_value, context, builder)?;
                            let result = builder.build_int_compare::<IntValue<'ctx>>(
                                match compare {
                                    CompareBinaryOperationNode::Greater => IntPredicate::UGT,
                                    CompareBinaryOperationNode::Lesser => IntPredicate::ULT,
                                    CompareBinaryOperationNode::GreaterEquals => IntPredicate::UGE,
                                    CompareBinaryOperationNode::LesserEquals => IntPredicate::ULE,
                                },
                                left_value.value,
                                right_value.value,
                                "",
                            )?;
                            Ok(SymbolItem::Bool { value: result })
                        }
                    }
                }
                ExpressionNode::Literal(literal) => Ok(SymbolItem::from_literal(context, literal)?),
                ExpressionNode::Symbol(name) => Ok(scope
                    .find_symbol(name.name)
                    .ok_or_else(|| CompileModuleError::UnknownSymbol(name.name.to_string()))?),
            }
        }

        #[allow(clippy::too_many_arguments)]
        fn compile_block<'ctx, 'input, Handler: RecordHandler>(
            compile_context: &mut CompileContext<'_, 'ctx, Handler>,
            context: &'ctx Context,
            functions: &HashMap<&str, FunctionValue<'ctx>>,
            matches: &HashMap<&str, &MatchNode>,
            module: &Module<'ctx>,
            scope: &mut GrammarScope<'_, 'input, SymbolItem<'ctx>>,
            builder: &Builder<'ctx>,
            node: &'input BlockNode<'input>,
            block_name: Option<&'input str>,
        ) -> Result<(BasicBlock<'ctx>, bool), CompileModuleError> {
            let env_start = compile_context
                .fn_value
                .get_first_param()
                .expect("Function don't have environment argument")
                .into_pointer_value();
            let env_length = compile_context
                .fn_value
                .get_nth_param(1)
                .expect("Function don't have length argument")
                .into_int_value();

            let init_block = context
                .append_basic_block(compile_context.fn_value, block_name.unwrap_or_default());
            builder.position_at_end(init_block);
            let mut current_block = init_block;
            let mut block_ended = false;
            for rule in &node.nodes {
                if block_ended {
                    return Err(CompileModuleError::TrailingOperation);
                }
                match rule {
                    RuleNode::Def(node) => {
                        let length = node.range.length.try_into().map_err(|_| {
                            CompileModuleError::InvalidIntSize(node.range.length as u32)
                        })?;

                        let env_start = builder.build_ptr_to_int(
                            env_start,
                            compile_context.ptr_sized_int_type,
                            "env_start_int",
                        )?;
                        let def_start = builder.build_int_add(
                            env_start,
                            compile_context
                                .ptr_sized_int_type
                                .const_int(node.range.start as u64, false),
                            &format!("def_start_int_{}", node.name),
                        )?;
                        let def_start = builder.build_int_to_ptr(
                            def_start,
                            compile_context.ptr_type,
                            &format!("def_start_{}", node.name),
                        )?;

                        let symbol_type = get_int_type(context, length)?;
                        let symbol = builder
                            .build_load(symbol_type, def_start, node.name)?
                            .into_int_value();
                        let symbol_item = SymbolItem::Int(IntSymbolItem {
                            size: length,
                            value: symbol,
                        });
                        scope.insert_symbol(node.name, symbol_item);
                    }
                    RuleNode::Let(node) => {
                        let symbol = build_expression(context, scope, builder, &node.expr)?;
                        scope.insert_symbol(node.name, symbol);
                    }
                    RuleNode::If(node) => {
                        let condition = build_expression(context, scope, builder, &node.expr)?;
                        let bool_value = condition.try_into_bool()?;

                        let mut then_scope = GrammarScope::new(scope);
                        let (then_block, then_block_ended) = compile_block(
                            compile_context,
                            context,
                            functions,
                            matches,
                            module,
                            &mut then_scope,
                            builder,
                            &node.block,
                            Some("then"),
                        )?;

                        let else_result = if let Some(else_node) = &node.else_block {
                            let mut else_scope = GrammarScope::new(scope);
                            let else_result = compile_block(
                                compile_context,
                                context,
                                functions,
                                matches,
                                module,
                                &mut else_scope,
                                builder,
                                else_node,
                                Some("else"),
                            )?;
                            Some(else_result)
                        } else {
                            None
                        };

                        builder.position_at_end(current_block);
                        if let Some((else_block, else_block_ended)) = else_result {
                            builder.build_conditional_branch(bool_value, then_block, else_block)?;
                            if then_block_ended && else_block_ended {
                                block_ended = true;
                                continue;
                            }
                            let continue_block =
                                context.append_basic_block(compile_context.fn_value, "continue");
                            if !then_block_ended {
                                builder.position_at_end(then_block);
                                builder.build_unconditional_branch(continue_block)?;
                            }
                            if !else_block_ended {
                                builder.position_at_end(else_block);
                                builder.build_unconditional_branch(continue_block)?;
                            }
                            builder.position_at_end(continue_block);
                            current_block = continue_block;
                        } else if !then_block_ended {
                            let continue_block =
                                context.append_basic_block(compile_context.fn_value, "continue");
                            builder.build_conditional_branch(
                                bool_value,
                                then_block,
                                continue_block,
                            )?;
                            builder.position_at_end(then_block);
                            builder.build_unconditional_branch(continue_block)?;

                            builder.position_at_end(continue_block);
                            current_block = continue_block;
                        }
                    }
                    RuleNode::Jump(node) => {
                        let new_env_start = if let Some(range) = &node.range {
                            if range.start != 0 {
                                let env_start = builder.build_ptr_to_int(
                                    env_start,
                                    compile_context.ptr_sized_int_type,
                                    "env_start_int",
                                )?;
                                let env_start = builder.build_int_add(
                                    env_start,
                                    compile_context
                                        .ptr_sized_int_type
                                        .const_int(range.start as u64, false),
                                    "new_env_start_int",
                                )?;
                                builder.build_int_to_ptr(
                                    env_start,
                                    compile_context.ptr_type,
                                    "new_env_start",
                                )?
                            } else {
                                env_start
                            }
                        } else {
                            env_start
                        };
                        let new_env_length = if let Some(range) = &node.range {
                            let length = compile_context
                                .ptr_sized_int_type
                                .const_int(range.length as u64, false);
                            if !range.reverse {
                                length
                            } else {
                                builder.build_int_sub(env_length, length, "new_env_len")?
                            }
                        } else {
                            env_length
                        };
                        let fn_args: [BasicMetadataValueEnum; 2] =
                            [new_env_start.into(), new_env_length.into()];

                        fn get_or_insert_proto_function<'ctx>(
                            fn_type: FunctionType<'ctx>,
                            module: &Module<'ctx>,
                            proto: &str,
                        ) -> FunctionValue<'ctx> {
                            let name = format!("{}_main", proto);
                            if let Some(func) = module.get_function(&name) {
                                func
                            } else {
                                module.add_function(&name, fn_type, Some(Linkage::External))
                            }
                        }

                        match &node.target {
                            JumpTarget::Chain(chain) => {
                                let target = *functions
                                    .get(&*format!("{}_{}", compile_context.proto_name, chain))
                                    .ok_or_else(|| {
                                        CompileModuleError::UnknownFunction(chain.to_string())
                                    })?;
                                builder.build_direct_call(target, &fn_args, "")?;
                            }
                            JumpTarget::Proto(proto) => {
                                let target = get_or_insert_proto_function(
                                    compile_context.fn_type,
                                    module,
                                    proto,
                                );
                                builder.build_direct_call(target, &fn_args, "")?;
                            }
                            JumpTarget::Match(match_item, expression) => {
                                let expression =
                                    build_expression(context, scope, builder, expression)?
                                        .try_into_int()?;
                                let match_item = *matches.get(match_item).ok_or_else(|| {
                                    CompileModuleError::UnknownMatch(match_item.to_string())
                                })?;

                                let else_block = context
                                    .append_basic_block(compile_context.fn_value, "unmatched");
                                builder.position_at_end(else_block);
                                builder.build_return(None)?;

                                let cases: Vec<_> = match_item
                                    .items
                                    .iter()
                                    .map(|item| {
                                        let value = SymbolItem::from_literal(context, &item.value)?;
                                        let value = value.try_into_int()?;
                                        let value =
                                            value.upcast(context, builder, expression.size)?.value;
                                        let call_block = context
                                            .append_basic_block(compile_context.fn_value, "match");

                                        let target = get_or_insert_proto_function(
                                            compile_context.fn_type,
                                            module,
                                            item.proto,
                                        );
                                        builder.position_at_end(call_block);
                                        builder.build_direct_call(target, &fn_args, "")?;
                                        builder.build_return(None)?;

                                        Ok::<_, CompileModuleError>((value, call_block))
                                    })
                                    .collect::<Result<_, CompileModuleError>>()?;

                                builder.position_at_end(current_block);
                                builder.build_switch(expression.value, else_block, &cases)?;
                            }
                        }
                        block_ended = true;
                    }
                    RuleNode::Stop => {
                        builder.build_return(None)?;
                        block_ended = true;
                    }
                    RuleNode::Record(record) => {
                        let record_item: RecordItem = record.into();

                        let id = compile_context.record_handler.emit_item(&record_item);
                        let id_value = context.i16_type().const_int(id as u64, false);

                        let size = record_item.items.len();
                        if size > u16::MAX.into() {
                            return Err(CompileModuleError::TooManyRecordItem(size));
                        }
                        let size = size as u16;
                        let size_value = context.i16_type().const_int(size as u64, false);

                        let buffer = builder.build_array_alloca(
                            context.i32_type(),
                            size_value,
                            &format!("buffer_pointer_record_{}", record_item.label),
                        )?;
                        let buffer_pointer = builder.build_ptr_to_int(
                            buffer,
                            compile_context.ptr_sized_int_type,
                            &format!("buffer_int_pointer_record_{}", record_item.label),
                        )?;
                        for (index, item) in record.content.iter().enumerate() {
                            let value = build_expression(context, scope, builder, &item.expr)?;
                            let value = value.try_into_int()?;
                            let value = value.upcast(context, builder, 4)?;
                            let field_pointer = builder.build_int_add(
                                buffer_pointer,
                                compile_context
                                    .ptr_sized_int_type
                                    .const_int((index * 4) as u64, false),
                                &format!("field_int_pointer_{}", item.label),
                            )?;
                            let field_pointer = builder.build_int_to_ptr(
                                field_pointer,
                                compile_context.ptr_type,
                                &format!("field_pointer_{}", item.label),
                            )?;
                            builder.build_store(field_pointer, value.value)?;
                        }

                        builder.build_direct_call(
                            compile_context.system_functions.emit_record,
                            &[id_value.into(), buffer.into(), size_value.into()],
                            &format!("call record type {}", record_item.label),
                        )?;
                    }
                }
            }
            Ok((init_block, block_ended))
        }

        for node in &ast.nodes {
            match node {
                ProtoNode::Chain(node) => {
                    let fn_value = functions.get(node.name).unwrap();
                    let mut scope = GrammarScope::default();
                    let builder = self.context.create_builder();
                    let mut compile_context = CompileContext {
                        system_functions: &system_functions,
                        proto_name,
                        record_handler,
                        ptr_type,
                        ptr_sized_int_type,
                        fn_type,
                        fn_value: *fn_value,
                    };
                    let (_, block_ended) = compile_block(
                        &mut compile_context,
                        self.context,
                        &functions,
                        &matches,
                        &module,
                        &mut scope,
                        &builder,
                        &node.body,
                        Some("entry"),
                    )?;
                    if !block_ended {
                        return Err(CompileModuleError::MissingStop);
                    }
                }
                ProtoNode::Match(_) => (),
            }
        }

        Ok(module)
    }
}

#[derive(Debug)]
pub struct LlvmIrStringTarget<'ctx> {
    module_target: &'ctx LlvmModuleTarget<'ctx>,
}

impl<'ctx> LlvmIrStringTarget<'ctx> {
    pub fn new(module_target: &'ctx LlvmModuleTarget<'ctx>) -> Self {
        Self { module_target }
    }
}

impl<'ctx> CompileTarget for LlvmIrStringTarget<'ctx> {
    type Result = String;

    type Error = CompileModuleError;

    fn compile_ast(
        &self,
        ast: &ProtoFile,
        name: &str,
        record_handler: &mut impl RecordHandler,
    ) -> Result<Self::Result, Self::Error> {
        let module = self.module_target.compile_ast(ast, name, record_handler)?;
        Ok(module.to_string())
    }
}

#[derive(Debug)]
pub struct LlvmBitcodeBufferTarget<'ctx> {
    module_target: &'ctx LlvmModuleTarget<'ctx>,
}

impl<'ctx> LlvmBitcodeBufferTarget<'ctx> {
    pub fn new(module_target: &'ctx LlvmModuleTarget<'ctx>) -> Self {
        Self { module_target }
    }
}

impl<'ctx> CompileTarget for LlvmBitcodeBufferTarget<'ctx> {
    type Result = Vec<u8>;

    type Error = CompileModuleError;

    fn compile_ast(
        &self,
        ast: &ProtoFile,
        name: &str,
        record_handler: &mut impl RecordHandler,
    ) -> Result<Self::Result, Self::Error> {
        let module = self.module_target.compile_ast(ast, name, record_handler)?;
        let buffer = module.write_bitcode_to_memory();
        Ok(buffer.as_slice().to_vec())
    }
}

#[derive(Debug)]
pub struct LlvmObjectTarget<'ctx> {
    module_target: &'ctx LlvmModuleTarget<'ctx>,
    path: PathBuf,
}

impl<'ctx> LlvmObjectTarget<'ctx> {
    pub fn new<P: AsRef<Path>>(module_target: &'ctx LlvmModuleTarget<'ctx>, path: P) -> Self {
        Self {
            module_target,
            path: path.as_ref().to_path_buf(),
        }
    }
}

#[derive(Debug)]
pub enum CompileObjectError {
    Module(CompileModuleError),
    Object(String),
}

impl<Target> CompileTargetError<Target> for CompileObjectError
where
    Target: CompileTarget<Error = CompileObjectError>,
{
    fn location(&self) -> Option<ErrorLocation> {
        match self {
            CompileObjectError::Module(error) => error.error_location(),
            CompileObjectError::Object(_) => None,
        }
    }
}

impl Error for CompileObjectError {}

impl Display for CompileObjectError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            CompileObjectError::Module(error) => Display::fmt(&error, f),
            CompileObjectError::Object(message) => {
                write!(f, "Failed to compile object: {}", message)
            }
        }
    }
}

impl From<CompileModuleError> for CompileObjectError {
    fn from(value: CompileModuleError) -> Self {
        CompileObjectError::Module(value)
    }
}

impl<'input, Target: CompileTarget> From<CompileObjectError> for CompileError<'input, Target>
where
    Target: CompileTarget<Error = CompileObjectError>,
{
    fn from(value: CompileObjectError) -> Self {
        CompileError::Compile(value)
    }
}

impl<'ctx> CompileTarget for LlvmObjectTarget<'ctx> {
    type Result = ();
    type Error = CompileObjectError;

    fn compile_ast<'input>(
        &self,
        ast: &'input ProtoFile,
        name: &'input str,
        record_handler: &mut impl RecordHandler,
    ) -> Result<Self::Result, Self::Error> {
        let module = self.module_target.compile_ast(ast, name, record_handler)?;
        module.set_data_layout(
            &self
                .module_target
                .machine
                .get_target_data()
                .get_data_layout(),
        );
        module.set_triple(&self.module_target.machine.get_triple());
        self.module_target
            .machine
            .write_to_file(&module, FileType::Object, &self.path)
            .map_err(|err| CompileObjectError::Object(err.to_string()))?;
        Ok(())
    }
}

#[derive(Debug)]
pub struct LlvmVecTarget<'ctx> {
    module_target: &'ctx LlvmModuleTarget<'ctx>,
}

impl<'ctx> LlvmVecTarget<'ctx> {
    pub fn new(module_target: &'ctx LlvmModuleTarget<'ctx>) -> Self {
        Self { module_target }
    }
}

impl<'ctx> CompileTarget for LlvmVecTarget<'ctx> {
    type Result = Vec<u8>;
    type Error = CompileObjectError;

    fn compile_ast<'input>(
        &self,
        ast: &'input ProtoFile,
        name: &'input str,
        record_handler: &mut impl RecordHandler,
    ) -> Result<Self::Result, Self::Error> {
        let module = self.module_target.compile_ast(ast, name, record_handler)?;
        module.set_data_layout(
            &self
                .module_target
                .machine
                .get_target_data()
                .get_data_layout(),
        );
        module.set_triple(&self.module_target.machine.get_triple());
        let buffer = self
            .module_target
            .machine
            .write_to_memory_buffer(&module, FileType::Object)
            .map_err(|err| CompileObjectError::Object(err.to_string()))?;
        Ok(buffer.as_slice().to_vec())
    }
}
